﻿
#region Using Directives.

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;
using BrightIdeasSoftware;
using GDFlacTool.Common;

#endregion

namespace GDFlacTool.Forms
{
    public partial class FormCreateMd5 : Form
    {
        #region Fields.

        private readonly string _md5FilePath;
        private readonly string _md5FileName;
        private readonly string _path;
        private readonly TrackMd5Collection _tracks = new TrackMd5Collection();
        private readonly TypedObjectListView<TrackMd5> _trackList;
        private int _index;
        private  HashType _hashType = HashType.Md5;
        private bool _isFinished;
        private int _errorCount;

        #endregion

        #region Delegates.

        private delegate void ProgressValueDelegate(GDFlacTools.Controls.Progress.ProgressBar progressBar, int val);
        private delegate void RedrawObjectListViewDelegate(int val1, int val2, bool val3);
        private delegate void SetLabelTextDelegate(Label lbl, string text);
        private delegate void SelectIndexDelegate(int val);

        private void SelectIndex(int index)
        {
            _trackList.SelectedObject = _tracks[index];
            _trackList.ListView.SelectedItem.EnsureVisible();
        }

        private void ProgressValue(GDFlacTools.Controls.Progress.ProgressBar progressBar, int value)
        {
            progressBar.Value = value;
        }

        private void SetLabelText(Label lbl, string text)
        {
            lbl.Text = text;
            lbl.Refresh();
        }

        private void RedrawObjectListView(int val1, int val2, bool val3)
        {
            objectListView.RedrawItems(val1, val2, val3);
            objectListView.RefreshObjects(_tracks);
        }

        #endregion

        #region Constructors.

        public FormCreateMd5(TrackMd5Collection tracks, string currentPath)
        {
            InitializeComponent();

            _trackList = new TypedObjectListView<TrackMd5>(objectListView);

            _tracks = tracks;

            _path = currentPath;

            _md5FileName = Functions.CommonPrefix(_tracks.GetFileNameArray().ToArray());

            string[] folders = _path.Split(Path.DirectorySeparatorChar);

            _md5FileName = folders[folders.Length - 1];

            _md5FileName = string.Format("{0}.md5", _md5FileName);

            _md5FilePath = Path.Combine(_path, _md5FileName);
        }

        #endregion

        #region Events.

        private void FormCheckMd5_Load(object sender, EventArgs e)
        {
            Text = string.Format("Create MD5 Sum [{0} File(s)]", _tracks.Count);

            lblPathValue.Text = string.Empty;

            lblNameValue.Text = string.Empty;

            lblFileSizeValue.Text = string.Empty;

            cboChecksumType.DataSource = typeof(HashType).ToList();
            cboChecksumType.DisplayMember = "Value";
            cboChecksumType.ValueMember = "Key";

            if (cboChecksumType.Items.Count > 0)
                cboChecksumType.SelectedValue = HashType.Md5;

            olvColumnStatus.Renderer = new MappedImageRenderer(new Object[] 
            { 
                KeyStatus.Unprocessed, imlStatus.Images[0], 
                KeyStatus.Processing, imlStatus.Images[1], 
                KeyStatus.Error, imlStatus.Images[2],
                KeyStatus.Done, imlStatus.Images[3]
            });

            objectListView.SetObjects(_tracks);
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            if (btnStart.Text == "&Start" || btnStart.Text == "&Restart")
            {
                Md5Worker.RunWorkerAsync(_hashType);

                btnStart.Text = "&Save";
                btnStart.ImageKey = "Save";

                btnStart.Enabled = false;

                btnClose.Text = "&Cancel";
                btnClose.ImageKey = "Cancel";

                cboChecksumType.Enabled = false;
            }
            else if (btnStart.Text == "&Save")
            {
                if (SaveMd5File())
                {
                    Close();
                }
            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            if (btnClose.Text == "&Cancel")
            {
                btnClose.Text = "&Close";
                btnClose.ImageKey = "Cancel";
                btnClose.Enabled = false;

                btnStart.Text = "&Restart";
                btnStart.ImageKey = "Ok";

                Md5Worker.CancelAsync();
            }
            else
            {
                Close();
            }
        }

        private void cboChecksumType_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(cboChecksumType.ValueMember))
                _hashType = (HashType)Enum.Parse(typeof(HashType), cboChecksumType.SelectedValue.ToString());
        }

        #endregion

        #region Private Methods.

        private bool SaveMd5File()
        {
            saveFileDialog.Title = @"Save Checksum Result As";
            saveFileDialog.Filter = "MD5Sum Files(*.md5)|*.md5";
            saveFileDialog.FilterIndex = 1;
            saveFileDialog.InitialDirectory = _path;
            saveFileDialog.FileName = _md5FileName;

            DialogResult dlgResult = saveFileDialog.ShowDialog(this);

            if (dlgResult == DialogResult.OK)
            {
                string text = GetMd5Checksums();

                StreamWriter streamWriter = null;

                try
                {
                    streamWriter = new StreamWriter(saveFileDialog.FileName);

                    streamWriter.Write(text);

                    return true;
                }
                catch (Exception ex)
                {
                    string message = string.Format("There was a problem saving the verification log [{0}]\n\n{1}", saveFileDialog.FileName, ex.Message);

                    MessageBox.Show(this, message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

                    return false;
                }
                finally
                {
                    if (streamWriter != null) streamWriter.Close();
                }
            }
                
            return false;
        }

        private string GetMd5Checksums()
        {
            var sb = new StringBuilder();
            const int counter = 1;

            string message1 = string.Format("; MD5 checksums generated by {0} version {1}", Application.ProductName, Application.ProductVersion);
            string message2 = string.Format("; Generated {0} {1}", DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString());

            sb.AppendLine(message1);
            sb.AppendLine(message2);
            sb.AppendLine();

            foreach (TrackMd5 track in _tracks)
            {
                sb.AppendFormat("{0} *{1}", track.Md5Hash.ToLower(), track.Filename);

                if (counter != _tracks.Count)
                {
                    sb.AppendLine();
                }
            }

            return sb.ToString();
        }

        private string GetHashString(HashType hashtype, string path)
        {
            int readBytes = -1;
            long num = 0;
            int val1 = 0;
            HashAlgorithm hashAlgorithm;

            string hashFile = path;

            FileStream stream = File.OpenRead(hashFile);

            long length = stream.Length;

            var buffer = new byte[0x2000];

            switch (hashtype)
            {
                case HashType.Md5:
                    hashAlgorithm = MD5.Create();
                    break;
                case HashType.Sha1:
                    hashAlgorithm = SHA1.Create();
                    break;
                case HashType.Sha256:
                    hashAlgorithm = SHA256.Create();
                    break;
                case HashType.Sha384:
                    hashAlgorithm = SHA384.Create();
                    break;
                case HashType.Sha512:
                    hashAlgorithm = SHA512.Create();
                    break;
                case HashType.Ripemd160:
                    hashAlgorithm = RIPEMD160.Create();
                    break;
                default:
                    hashAlgorithm = MD5.Create();
                    break;
            }

            while (readBytes != 0)
            {
                readBytes = stream.Read(buffer, 0, buffer.Length);

                hashAlgorithm.TransformBlock(buffer, 0, readBytes, null, 0);

                num += readBytes;

                var val = (int)(((num) / ((double)length)) * 100.0);

                Invoke(new ProgressValueDelegate(ProgressValue), new object[] { prbProcess, val });

                val1++;
            }

            string str = string.Empty;

            hashAlgorithm.TransformFinalBlock(buffer, 0, 0);

            byte[] buffer2 = hashAlgorithm.Hash;

            for (int j = 0; j < buffer2.Length; j++)
            {
                str = str + string.Format("{0:X2}", buffer2[j]);
            }

            return str;
        }

        private int CheckMd5Hash(HashType hashType)
        {
            for (int i = _index; i < _tracks.Count; ++i)
            {
                _tracks[i].Status = KeyStatus.Processing;

                Invoke(new RedrawObjectListViewDelegate(RedrawObjectListView), new object[] { i, i, false });

                string filepath = _tracks[i].Filepath;

                if (!File.Exists(filepath))
                {
                    _tracks[i].Status = KeyStatus.Error;

                    _tracks[i].Logmessage = string.Format("{0} - ERROR: {1} does not exists.", _tracks[i].Filename, _tracks[i].Filepath);

                    _errorCount++;

                    Invoke(new RedrawObjectListViewDelegate(RedrawObjectListView), new object[] { i, i, false });
                }
                else
                {
                    Invoke(new SetLabelTextDelegate(SetLabelText), new object[] { lblPathValue, Functions.ShortenName(filepath, 80) });

                    Invoke(new SetLabelTextDelegate(SetLabelText), new object[] { lblNameValue, _tracks[i].Filename });

                    FileInfo fi = new FileInfo(_tracks[i].Filepath);

                    Invoke(new SetLabelTextDelegate(SetLabelText), new object[] { lblFileSizeValue, Functions.GetFileArtSizeFromBytes(fi.Length) });

                    _tracks[i].Md5Hash = GetHashString(hashType, _tracks[i].Filepath);

                    if(!string.IsNullOrEmpty(_tracks[i].Md5Hash))
                    {
                        _tracks[i].Md5Hash = _tracks[i].Md5Hash.ToLower();
                    }

                    _tracks[i].Status = KeyStatus.Done;

                    Invoke(new RedrawObjectListViewDelegate(RedrawObjectListView), new object[] { i, i, false });

                    Invoke(new SelectIndexDelegate(SelectIndex), new object[] { i });
                }

                var val = (int)(((i + 1) / ((double)_tracks.Count)) * 100.0);

                Invoke(new ProgressValueDelegate(ProgressValue), new object[] { prbTotal, val });

                string message1 = string.Format("{0} Error(s) in {1} processed)", _errorCount, i + 1);

                Invoke(new SetLabelTextDelegate(SetLabelText), new object[] { lblProcessedError, message1 });

                string message2 = string.Format("Batch ( {0}/{1} ):", i + 1, _tracks.Count);

                Invoke(new SetLabelTextDelegate(SetLabelText), new object[] { lblBatch, message2 });

                _index++;

                if (i == _tracks.Count - 1)
                    _isFinished = true;

                if (Md5Worker.CancellationPending)
                    break;
            }

            return _errorCount;
        }

        #endregion

        #region Async Events.

        private void Md5Worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            HashType type = (HashType)e.Argument;

            _errorCount = CheckMd5Hash(type);
        }

        private void Md5Worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            btnStart.Enabled = true;
            btnClose.Enabled = true;
            btnClose.Text = "&Close";
            btnClose.ImageKey = "Cancel";

            if (_isFinished)
            {
                prbProcess.Value = 0;
                prbTotal.Value = 0;
                SaveMd5File();
            }

            if (_isFinished && _errorCount > 0)
            {
                string message = string.Format("There were {0} errors while processing the files.", _errorCount);

                MessageBox.Show(this, message, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        #endregion
    }
}
